Release 10.1A: OpenEdge Development:
Progress Dynamics Basic Development


New Progress Dynamics validation procedures

Progress Dynamics also supports additional standard internal procedure hooks that let you write validation logic for specific operations (Create, Write, and Delete), one record at a time. This spares you from writing a FOR EACH loop in each validation procedure to allow for the possibility that more than one row is being updated in a single transaction. It also removes the need to check the value of RowMod or in any other way write code specific to the structure of the RowObjUpd table. Among other things, this structure makes it possible to use the same logic procedure to validate a buffer outside the context of an SDO.

For each stage in the transaction as described above (preTransaction, beginTransaction, endTransaction, and postTransaction), three procedure names are defined which, if they exist, will be called to deal with specific operations a row at a time. This, then, gives you a total of twelve additional hooks that you can use as needed:

These hooks are called exactly when you would expect, based on their names. The create group hooks are called instead of their pre/begin/end/post counterparts for each newly created row, that is, a row that results from either an add or copy operation.

The hooks in the write group are called for each update, add, or copy operation, the delete group for each delete operation.

To make the logic as general as possible, and to support calling this validation logic from outside of SDOs, the buffer name of the table being updated is not RowObjUpd but rather the table name preceded by b_. This buffer name is available to the internal procedure. You do not need to define it there or pass it in as a parameter. (These internal procedures take no parameters at all.) In the case of the write group of procedures, the unchanged version of the record as it was originally read from the database is also available in a buffer with the table name prefixed by old_.

In addition, because the write procedures will be called for adds and copies as well as updates (since you will want to place business logic there to validate values being written to the database for all of these cases), there is an additional logical variable named isCreate. The isCreate variable is available within the validation procedures. It will be TRUE if the operation is an add or copy, and FALSE otherwise.

Note that the client-side validation procedure rowObjectValidate has not been subdivided into multiple other procedure hooks. This is partly because it is called for each individual Save operation, and therefore never needs to refer to multiple records as the server-side procedures must be prepared to do. Also, the principal goal of the logic procedure is to make the server-side logic, where most code is expected to go, as flexible and reusable as possible. However, the b_ + <tablename> buffer is defined for rowObjectValidate and available to it, so that the developer does not have to write references to the RowObject buffer.

The framework gives you the option of using one or the other of these two sets of procedures in a single SDO, but not both. That is, an SDO (and its supporting logic procedure if it has one) can use the pre/begin/end/postTransactionValidate procedures as in the standard ADM. Alternatively, it can use the new create/write/delete set of procedure names. It cannot use both (this is largely to avoid confusing issues of the exact order in which all the procedures would be executed).

In general, the recommendation is that new application objects should use the new procedures. They are more fine-grained. That is, they give you logic for a more specific update type and free you from the RowObjUpd and RowMod references of the other procedures. Also, they are more reusable from elsewhere in your application. The TransactionValidate procedures provide compatibility with existing Progress Version 9 applications.

Chapter 5, "Using the Object Generator," shows some examples of the code for these new procedures, as automatically generated for you by the Object Generator. Here is an example:

PROCEDURE deletePreTransValidate:  
/*------------------------------------------------------------------------ 
 Purpose:   Procedure used to validate records server-side before the  
        transaction scope on delete of Order 
 Parameters: <none> 
 Notes:    Restrict delete of Order with OrderLine records. 
-------------------------------------------------------------------------*/ 
 IF CAN-FIND(FIRST OrderLine 
       WHERE OrderLine.OrderNum = b_Order.OrderNum) THEN 
 DO: 
   ERROR-STATUS:ERROR = NO. 
   RETURN {aferrortxt.i 'OE' '8' 'Order' '' STRING(b_Order.OrderNum)}. 
 END. 
END PROCEDURE. 

This code checks to see if there is an existing OrderLine for the Order being deleted. If there is, the delete is rejected. The use of the standard error-formatting include file aferrortxt.i is explained in more detail in the "Message handling in Progress Dynamics" section. In this case, error message number 8 from the Order Entry (OE) group is returned with a substitution argument indicating the Order Number. So the message as stored in the message table in the Repository is something like: Can’t delete Order Number <&1>, which has Order Lines.

The standard Progress 4GL ERROR-STATUS is reset as a matter of course, because the error- handling code will rely on the message being returned as the error flag.

Note the use of the database table name OrderLine to refer to the database lookup, and the SDO buffer name b_Order to refer to the record being deleted.

The following example shows the use of the Old buffer holding the original record as read from the database:

PROCEDURE writePreTransValidate:  
/*------------------------------------------------------------------------ 
 Purpose:   Procedure used to validate records server-side before the  
        transaction scope on write 
 Parameters: <none> 
 Notes:     
-------------------------------------------------------------------------*/ 
 DEFINE VARIABLE cMessageList  AS CHARACTER  NO-UNDO. 
 DEFINE VARIABLE cValueList   AS CHARACTER  NO-UNDO. 
 IF b_Customer.CreditLimit – Old_Customer.CreditLimit > 10000 THEN 
 DO: 
   ASSIGN 
    cValueList  = STRING(b_Customer.CreditLimit) 
    cMessageList = cMessageList +  
 (IF NUM-ENTRIES(cMessageList,CHR(3)) > 0 THEN CHR(3) ELSE '':U) +  
     {aferrortxt.i 'CM' '8' 'Customer' 'CreditLimit' cValueList }. 
 END. 
  
   
 ERROR-STATUS:ERROR = NO. 
 RETURN cMessageList. 
END PROCEDURE. 

In this example, the CreditLimit of the original version of the record as read from the database is compared with the modified value. If this has been increased by more than $10,000, the change is rejected, using message 8 in the Customer Maintenance (CM) message group.

Note the accumulation of messages here. More than one message can be returned to the client, delimited by CHR(3). In this case, the ellipsis represents additional error checks that might be done. If the intent is to check for all possible errors and return a list of them, rather than returning the first error encountered, then this coding style accomplishes that. Each error text is added to a variable, and the entire list is returned to the client where it will be properly parsed and presented to the user.

Note: If you want to implement initialization functionality for data-handling objects (SDOs, SBOs), you can override the createobjects() method of the container. Override with a check for Valid-Handle and access-contained instances. This technique allows you to add functionality before the main initialization.

It is advisable to check all forms of validity in application code, even if there exists either a database trigger procedure or, as in this case, a built-in database check to perform the validation for you. If your code catches an error such as this, you can return a meaningful (and translatable) message to the user rather than waiting for Progress to attempt to display what might be a much less meaningful message, and one that might not make it back to the client successfully. And your code can react to the error exactly as you want, rather than trapping default Progress behavior.


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095